﻿#include "Mesh.h"

#include <utility>

#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>

#include "Debuger.h"
#include "Light/Light.h"
#include "MaterialSrc/MeshMaterial.h"

bool FMesh::Init(
	std::vector<FVertexInfo>&& VertexArr,
	std::vector<int32_t>&& IndexArr,
	std::shared_ptr<FMaterial>&& InMaterial)
{
	VertexArray = std::move(VertexArr);
	IndexArray = std::move(IndexArr);
	Material = std::move(InMaterial);
	UsedIndexDraw = true;

	this->InitVertexInfo();
	this->InitIndexInfo();

	const bool Ok = Material->Init();
	if (Ok)
	{
		this->OnInit();
	}
	return Ok;
}

bool FMesh::Init(const std::vector<FVertexInfo>& VertexArr, std::shared_ptr<FMaterial> InMaterial)
{
	VertexArray = VertexArr;
	Material = InMaterial;
	InitVertexInfo();

	const bool Ok = Material->Init();
	if (Ok)
	{
		this->OnInit();
	}
	return Ok;
}

bool FMesh::Init(std::vector<FVertexInfo>&& VertexArr, std::shared_ptr<FMaterial>&& InMaterial)
{
	VertexArray = std::move(VertexArr);
	Material = std::move(InMaterial);
	InitVertexInfo();

	const bool Ok = Material->Init();
	if (Ok)
	{
		this->OnInit();
	}
	return Ok;
}


bool FMesh::InitVertexInfo()
{
	if (VAO == 0)
	{
		glGenVertexArrays(1, &VAO);
	}

	glBindVertexArray(VAO);

	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, static_cast<int64_t>(VertexArray.size() * sizeof(FVertexInfo)), VertexArray.data(), GL_STATIC_DRAW);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(FVertexInfo), (void*)offsetof(FVertexInfo, Position));
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(FVertexInfo), (void*)offsetof(FVertexInfo, Normal));
	glEnableVertexAttribArray(1);

	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(FVertexInfo), (void*)offsetof(FVertexInfo, TexCoords));
	glEnableVertexAttribArray(2);

	return true;
}

bool FMesh::InitIndexInfo()
{
	if (VAO == 0)
	{
		glGenVertexArrays(1, &VAO);
	}

	glBindVertexArray(VAO);
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast<int64_t>(IndexArray.size() * sizeof(int32_t)), IndexArray.data(), GL_STATIC_DRAW);

	return  true;
}



bool FMesh::InitVertex(const std::vector<FVertexInfo>& VertexArr)
{
	VertexArray = VertexArr;
	return InitVertexInfo();
}

bool FMesh::InitIndex(const std::vector<int32_t>& IndexArr)
{
	IndexArray = IndexArr;
	return InitIndexInfo();
}



void FMesh::Draw(const FDrawArg& DrawArg) const
{
	Material->Use();

	const auto Shader = Material->GetShader();

	// 把光照数据传递给 Shader 
	if (Shader->GetLightingModel() != ELightingModel::None)
	{
		for (const std::shared_ptr<FLightBase>& Light : DrawArg.LightArray)
		{
			Light->ApplyValueToShader(Light->GetUniformStructName(), Shader);
		}
	}

	std::map<std::string, bool> EnabledSetData{
		{"view", true},
		{"projection", true},
		{"model", true},

		// 默认 只有需要高光效果时 才用提供 ViewPos
		{"viewPos", Shader->GetLightingModel() == ELightingModel::DiffuseShininess},
	};

	if (OnCheckEnabledSetShaderData)
	{
		OnCheckEnabledSetShaderData(EnabledSetData);
	}

	// 传递当前的 视图信息
	if (EnabledSetData["view"])
	{
		Shader->SetMat4("view", DrawArg.ViewMat);
	}

	if (EnabledSetData["projection"])
	{
		Shader->SetMat4("projection", DrawArg.ProjectionMat);
	}

	if (EnabledSetData["model"])
	{
		Shader->SetMat4("model", Transform);
	}

	if (EnabledSetData["viewPos"])
	{
		Shader->SetVec3("viewPos", DrawArg.ViewPos);
	}

	if (OnSetShaderData)
	{
		OnSetShaderData(Shader);
	}

	// 绘制
	glBindVertexArray(VAO);

	if (IndexArray.size() > 0)
	{
		glDrawElements(GL_TRIANGLES, static_cast<int>(IndexArray.size()), GL_UNSIGNED_INT, nullptr);
	}
	else
	{
		glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexArray.size()));
	}

	glBindVertexArray(0);
}

bool FMeshModel::Load(const std::filesystem::path& ModelAssetDirPath, const std::string& AssetFileName)
{
	if (!std::filesystem::exists(ModelAssetDirPath / AssetFileName))
	{
		return false;
	}

	AssetDirPath = ModelAssetDirPath;

	Assimp::Importer Import;
	const auto Scence = Import.ReadFile((ModelAssetDirPath / AssetFileName).string().c_str(), aiProcess_Triangulate | aiProcess_FlipUVs);

	if (!Scence || (Scence->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !Scence->mRootNode)
	{
		DebugErr("LoadModel 错误 [%hs] %hs ", AssetFileName.c_str(), "  ss");
		return false;
	}

	try
	{
		this->ProcessNode(Scence->mRootNode, Scence);
	}
	catch (...)
	{
		DebugErr("LoadModel 错误 [%hs] %hs", AssetFileName.c_str(), "ProcessNode");
		return false;
	}

	return true;
}

void FMeshModel::Draw(const FDrawArg& DrawArg) const
{
	for (auto& Mesh : MeshArray)
	{
		Mesh->Draw(DrawArg);
	}
}

std::shared_ptr<FMesh> FMeshModel::CreateMesh() const
{
	return std::make_shared<FMesh>();
}

std::shared_ptr<FMaterial> FMeshModel::CreateMaterial(ETextureType Type) const
{
	if (CustomCreateMaterialCall)
	{
		return CustomCreateMaterialCall(Type);
	}


	if (Type == ETextureType::Diffuse)
	{
		return 	std::make_shared<FMeshMaterialDiffuse>();
	}
	else if (Type == ETextureType::DiffuseShininessr)
	{
		return std::make_shared<FMeshMaterialDiffuseShininessr>();
	}
	return nullptr;
}

void FMeshModel::ProcessNode(const aiNode* Node, const aiScene* Scene)
{
	MeshArray.reserve(Node->mNumMeshes);
	for (uint32_t Index = 0; Index < Node->mNumMeshes; Index++)
	{
		const aiMesh* Mesh = Scene->mMeshes[Node->mMeshes[Index]];
		MeshArray.push_back(this->ProcessMesh(Mesh, Scene));
	}

	for (uint32_t Index = 0; Index < Node->mNumChildren; Index++)
	{
		this->ProcessNode(Node->mChildren[Index], Scene);
	}
}

void FMeshModel::LoadMaterialTextures(const aiMaterial* Mat, const aiTextureType Type, const std::string TypeName, std::vector<FTextureInfo>& OutTextureInfoArr) const
{
	for (unsigned int I = 0; I < Mat->GetTextureCount(Type); I++)
	{
		FTextureInfo Info;
		aiString Str;
		Mat->GetTexture(Type, I, &Str);
		Info.TexturePath = AssetDirPath / Str.C_Str();
		Info.TypeName = TypeName;
		Info.Index = I;
		OutTextureInfoArr.push_back(Info);
	}
}

std::shared_ptr<FMesh> FMeshModel::ProcessMesh(const aiMesh* Mesh, const aiScene* Scene) const
{
	std::vector<FVertexInfo> VertexInfoArr;
	std::vector<int32_t> IndexArr;

	VertexInfoArr.reserve(Mesh->mNumVertices);
	// 预估
	IndexArr.reserve(static_cast<std::vector<int>::size_type>(Mesh->mNumFaces) * 4);

	// 顶点
	for (uint32_t Index = 0; Index < Mesh->mNumVertices; Index++)
	{
		const auto& Vertices = Mesh->mVertices[Index];
		const auto& Normal = Mesh->mNormals[Index];

		glm::vec2 TempTextureCoords(0, 0);
		if (Mesh->mTextureCoords[0])
		{
			const auto& TextureCood = Mesh->mTextureCoords[0][Index];
			TempTextureCoords.x = static_cast<float>(TextureCood.x);
			TempTextureCoords.y = static_cast<float>(TextureCood.y);
		}

		VertexInfoArr.emplace_back(
			glm::vec3(Vertices.x, Vertices.y, Vertices.z),
			glm::vec3(Normal.x, Normal.y, Normal.z),
			TempTextureCoords
		);
	}

	// 索引
	for (uint32_t Index = 0; Index < Mesh->mNumFaces; Index++)
	{
		const auto& Face = Mesh->mFaces[Index];
		for (uint32_t I = 0; I < Face.mNumIndices; I++)
		{
			IndexArr.push_back(static_cast<int32_t>(Face.mIndices[I]));
		}
	}
	IndexArr.shrink_to_fit();

	// 材质
	std::shared_ptr<FMaterial> Material;
	if (Mesh->mMaterialIndex > 0)
	{
		std::vector<FTextureInfo> TextureInfos;
		const auto Mat = Scene->mMaterials[Mesh->mMaterialIndex];

		LoadMaterialTextures(Mat, aiTextureType_DIFFUSE, "textureDiffuse", TextureInfos);

		LoadMaterialTextures(Mat, aiTextureType_SPECULAR, "textureSpecular", TextureInfos);

		const auto ModelMaterial = this->CreateMaterialInstance<FMeshMaterialBase>(
			TextureInfos.size() > 1 ? ETextureType::DiffuseShininessr : ETextureType::Diffuse);

		ModelMaterial->SetTextureInfoArr(std::move(TextureInfos));
		Material = ModelMaterial;

	}

	// ReSharper disable once CppTooWideScopeInitStatement
	auto MeshPtr = this->CreateMesh();
	if (MeshPtr->Init(std::move(VertexInfoArr), std::move(IndexArr), std::move(Material)))
	{
		return MeshPtr;
	}

	return nullptr;
}
